/*
 * Created on 02-Aug-2004
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package org.gudy.azureus2.core3.disk.impl.piecemapper.impl;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.gudy.azureus2.core3.disk.impl.DiskManagerFileInfoImpl;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceList;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMap;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMapper;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMapperFile;
import org.gudy.azureus2.core3.internat.LocaleUtilDecoder;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentFile;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.core3.util.StringInterner;

/**
 * @author parg
 * 
 */

public class PieceMapperImpl implements DMPieceMapper {
    private TOTorrent torrent;

    private int last_piece_length;

    protected ArrayList<fileInfo> btFileList = new ArrayList<fileInfo>();

    public PieceMapperImpl(TOTorrent _torrent) {
        torrent = _torrent;

        int piece_length = (int) torrent.getPieceLength();

        int piece_count = torrent.getNumberOfPieces();

        long total_length = torrent.getSize();

        last_piece_length = (int) (total_length - ((long) (piece_count - 1) * (long) piece_length));
    }

    public void construct(LocaleUtilDecoder _locale_decoder, String _save_name)

    throws UnsupportedEncodingException {
        // build something to hold the filenames/sizes

        TOTorrentFile[] torrent_files = torrent.getFiles();

        if (torrent.isSimpleTorrent()) {

            buildFileLookupTables(torrent_files[0], _save_name);

        } else {

            buildFileLookupTables(torrent_files, _locale_decoder);
        }
    }

    // method for simple torrents

    private void buildFileLookupTables(TOTorrentFile torrent_file, String fileName) {
        // not needed as fileName already normalised
        // fileName = FileUtil.convertOSSpecificChars( fileName, false );

        btFileList.add(new PieceMapperImpl.fileInfo(torrent_file, "", fileName));
    }

    private void buildFileLookupTables(TOTorrentFile[] torrent_files, LocaleUtilDecoder locale_decoder)

    throws UnsupportedEncodingException {
        char separator = File.separatorChar;

        // for each file

        for (int i = 0; i < torrent_files.length; i++) {

            buildFileLookupTable(torrent_files[i], locale_decoder, separator);
        }
    }

    /**
     * Builds the path stored in fileDictionay, saving it in btFileList
     * 
     * @param fileDictionay
     * @param btFileList
     * @param localeUtil
     * @param separator
     * @return the length of the file as stored in fileDictionay
     */
    // refactored out of initialize() - Moti
    // code further refactored for readibility

    private void buildFileLookupTable(TOTorrentFile torrent_file, LocaleUtilDecoder locale_decoder, final char separator)

    throws UnsupportedEncodingException {
        // build the path

        byte[][] path_components = torrent_file.getPathComponents();

        /*
         * replaced the following two calls: StringBuffer pathBuffer = new StringBuffer(256); pathBuffer.setLength(0);
         */
        StringBuffer pathBuffer = new StringBuffer(0);

        int lastIndex = path_components.length - 1;
        for (int j = 0; j < lastIndex; j++) {
            // attach every element

            String comp = locale_decoder.decodeString(path_components[j]);

            comp = FileUtil.convertOSSpecificChars(comp, true);

            pathBuffer.append(comp);
            pathBuffer.append(separator);
        }

        // no, then we must be a part of the path
        // add the file entry to the file holder list

        String last_comp = locale_decoder.decodeString(path_components[lastIndex]);

        last_comp = FileUtil.convertOSSpecificChars(last_comp, false);

        btFileList.add(new fileInfo(torrent_file, pathBuffer.toString(), last_comp));
    }

    public DMPieceMap getPieceMap() {
        if (btFileList.size() == 1) {

            // optimise for the single file case

            return (new DMPieceMapSimple(torrent, ((fileInfo) btFileList.get(0)).getFileInfo()));

        } else {
            int piece_length = (int) torrent.getPieceLength();

            int piece_count = torrent.getNumberOfPieces();

            long total_length = torrent.getSize();

            DMPieceList[] pieceMap = new DMPieceList[piece_count];

            // for every piece, except the last one
            // add files to the piece list until we have built enough space to hold the piece
            // see how much space is available in the file
            // if the space available isnt 0
            // add the file to the piece->file mapping list
            // if there is enough space available, stop

            // fix for 1 piece torrents

            int modified_piece_length = piece_length;

            if (total_length < modified_piece_length) {

                modified_piece_length = (int) total_length;
            }

            long fileOffset = 0;
            int currentFile = 0;
            for (int i = 0; (1 == piece_count && i < piece_count) || i < piece_count - 1; i++) {
                ArrayList<PieceMapEntryImpl> pieceToFileList = new ArrayList<PieceMapEntryImpl>();
                int usedSpace = 0;
                while (modified_piece_length > usedSpace) {
                    fileInfo tempFile = (fileInfo) btFileList.get(currentFile);
                    long length = tempFile.getLength();

                    // get the available space
                    long availableSpace = length - fileOffset;

                    PieceMapEntryImpl tempPieceEntry = null;

                    // how much space do we need to use?
                    if (availableSpace <= (modified_piece_length - usedSpace)) {
                        // use the rest of the file's space
                        tempPieceEntry = new PieceMapEntryImpl(tempFile.getFileInfo(), fileOffset, (int) availableSpace // safe to convert here
                                );

                        // update the used space
                        usedSpace += availableSpace;
                        // update the file offset
                        fileOffset = 0;
                        // move the the next file
                        currentFile++;
                    } else // we don't need to use the whole file
                    {
                        tempPieceEntry = new PieceMapEntryImpl(tempFile.getFileInfo(), fileOffset, modified_piece_length - usedSpace);

                        // update the file offset
                        fileOffset += modified_piece_length - usedSpace;
                        // udate the used space
                        usedSpace += modified_piece_length - usedSpace;
                    }

                    // add the temp pieceEntry to the piece list
                    pieceToFileList.add(tempPieceEntry);
                }

                // add the list to the map
                pieceMap[i] = PieceListImpl.convert(pieceToFileList);
            }

            // take care of final piece if there was more than 1 piece in the torrent
            if (piece_count > 1) {
                pieceMap[piece_count - 1] = PieceListImpl.convert(buildLastPieceToFileList(btFileList, currentFile, fileOffset));

            }

            return (new DMPieceMapImpl(pieceMap));
        }
    }

    private List<PieceMapEntryImpl> buildLastPieceToFileList(List<fileInfo> file_list, int current_file, long file_offset) {
        ArrayList<PieceMapEntryImpl> piece_to_file_list = new ArrayList<PieceMapEntryImpl>();

        for (int i = current_file; i < file_list.size(); i++) {

            fileInfo file = file_list.get(i);

            long space_in_file = file.getLength() - file_offset;

            PieceMapEntryImpl piece_entry = new PieceMapEntryImpl(file.getFileInfo(), file_offset, (int) space_in_file);

            piece_to_file_list.add(piece_entry);

            file_offset = 0;
        }

        return (piece_to_file_list);
    }

    public long getTotalLength() {
        return (torrent.getSize());
    }

    public int getPieceLength() {
        return ((int) torrent.getPieceLength());
    }

    public int getLastPieceLength() {
        return (last_piece_length);
    }

    public DMPieceMapperFile[] getFiles() {
        DMPieceMapperFile[] res = new DMPieceMapperFile[btFileList.size()];

        btFileList.toArray(res);

        return (res);
    }

    protected static class fileInfo implements DMPieceMapperFile {
        private DiskManagerFileInfoImpl file;
        private TOTorrentFile torrent_file;
        private String path;
        private String name;

        public fileInfo(TOTorrentFile _torrent_file, String _path, String _name) {
            torrent_file = _torrent_file;
            path = StringInterner.intern(_path);
            name = _name;
        }

        public long getLength() {
            return torrent_file.getLength();
        }

        public File getDataFile() {
            return (new File(path, name));
        }

        public TOTorrentFile getTorrentFile() {
            return (torrent_file);
        }

        public DiskManagerFileInfoImpl getFileInfo() {
            return file;
        }

        public void setFileInfo(DiskManagerFileInfoImpl _file) {
            file = _file;
        }
    }
}
